package com.vdn.gateway.route;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.utils.StringUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Nacos 动态路由管理
 * <p>
 * 该类用于管理 Spring Cloud Gateway 的路由，并将其存储在 Nacos 配置中心中。
 * 通过监听 Nacos 配置的变更，实现网关路由的动态刷新。
 */
@Component
public class NacosRouteDefinitionRepository implements RouteDefinitionRepository {
    private final Logger log = LoggerFactory.getLogger(NacosRouteDefinitionRepository.class);

    // 用于发布 Spring 事件（刷新路由）
    private final ApplicationEventPublisher publisher;

    // Nacos 配置属性
    private final NacosConfigProperties nacosConfigProperties;

    // Nacos 配置管理器
    private final NacosConfigManager nacosConfigManager;

    // JSON 解析工具
    private final ObjectMapper objectMapper;

    // Nacos 中存储路由的 `dataId`
    private static final String DATA_ID = "gateway-routes.json";

    // 配置获取超时时间（单位：毫秒）
    private static final int CONFIG_TIMEOUT_MS = 3000;

    /**
     * 构造方法
     *
     * @param publisher            Spring 事件发布器，用于动态刷新网关路由
     * @param nacosConfigProperties Nacos 配置属性
     */
    @Autowired
    public NacosRouteDefinitionRepository(ApplicationEventPublisher publisher, NacosConfigProperties nacosConfigProperties) {
        this.publisher = publisher;
        this.nacosConfigProperties = nacosConfigProperties;
        this.nacosConfigManager = new NacosConfigManager(nacosConfigProperties);
        this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // 初始化 Nacos 配置监听器
        nacosListener();
    }

    /**
     * Nacos 配置监听器
     * <p>
     * 监听 Nacos 中 `gateway-routes.json` 发生变更时，触发 Gateway 重新加载路由。
     */
    private void nacosListener() {
        // 使用单线程线程池，避免 Listener 需要 Executor 时出错
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        try {
            nacosConfigManager.getConfigService().addListener(DATA_ID, nacosConfigProperties.getGroup(), new Listener() {
                @Override
                public Executor getExecutor() {
                    return executorService;
                }

                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("收到新的路由配置: {}", configInfo);
                    // 发布 Spring 事件，触发网关路由刷新
                    publisher.publishEvent(new RefreshRoutesEvent(this));
                }
            });
        } catch (NacosException e) {
            log.error("Nacos 监听器初始化失败", e);
        } finally {
            // 关闭线程池，避免资源泄露
            executorService.shutdown();
        }
    }

    /**
     * 获取 Nacos 中的路由配置
     * <p>
     * 该方法会从 Nacos 读取 `gateway-routes.json` 配置，并解析为 RouteDefinition 列表。
     *
     * @return 返回 Flux<RouteDefinition>，用于 Gateway 加载路由
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        try {
            // 从 Nacos 读取配置
            String routeConfig = nacosConfigManager.getConfigService()
                    .getConfig(DATA_ID, nacosConfigProperties.getGroup(), CONFIG_TIMEOUT_MS);

            // 路由列表
            List<RouteDefinition> routeDefinitionList = new ArrayList<>();

            // 如果配置不为空，则解析 JSON
            if (StringUtils.hasText(routeConfig)) {
                routeDefinitionList = objectMapper.readValue(routeConfig, new TypeReference<>() {
                });
            }

            return Flux.fromIterable(routeDefinitionList);
        } catch (Exception e) {
            log.error("从 Nacos 获取路由定义失败", e);
            return Flux.error(e);
        }
    }

    /**
     * 保存路由定义
     * <p>
     * 该方法会将新的路由定义追加到 `gateway-routes.json` 中，并同步更新到 Nacos。
     *
     * @param route 需要保存的路由定义
     * @return 返回 Mono<Void>
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(r -> {
            try {
                // 将新的 RouteDefinition 转换为 JSON
                String routeJson = objectMapper.writeValueAsString(r);
                // 发布到 Nacos
                nacosConfigManager.getConfigService().publishConfig(DATA_ID, nacosConfigProperties.getGroup(), routeJson);
                return Mono.empty();
            } catch (Exception e) {
                log.error("保存路由定义失败", e);
                return Mono.error(e);
            }
        });
    }

    /**·
     * 删除路由定义
     * <p>
     * 该方法会从 `gateway-routes.json` 中移除指定的路由 ID，并同步更新 Nacos。
     *
     * @param routeId 需要删除的路由 ID
     * @return 返回 Mono<Void>
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            try {
                // 获取 Nacos 中的路由配置
                String routeConfig = nacosConfigManager.getConfigService()
                        .getConfig(DATA_ID, nacosConfigProperties.getGroup(), CONFIG_TIMEOUT_MS);

                // 解析 JSON 为 RouteDefinition 列表
                List<RouteDefinition> routeDefinitionList = objectMapper.readValue(routeConfig, new TypeReference<>() {
                });

                // 删除指定 ID 的路由
                routeDefinitionList.removeIf(rd -> rd.getId().equals(id));

                // 重新生成 JSON 并更新到 Nacos
                String updatedRouteJson = objectMapper.writeValueAsString(routeDefinitionList);
                nacosConfigManager.getConfigService().publishConfig(DATA_ID, nacosConfigProperties.getGroup(), updatedRouteJson);

                return Mono.empty();
            } catch (Exception e) {
                log.error("删除路由定义失败，ID: {}", id, e);
                return Mono.error(e);
            }
        });
    }
}
